/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package TimeRecording.business;

import TimeRecording.enums.PartOfDay;
import java.awt.Color;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
 *
 * @author david
 */
public class DaisyChainUtilities {

    public final static Color PALE_GREEN = new Color(0x7F,0xFF,0x7F);
    public final static Color PALE_RED = new Color(0xFF,0x7F,0x7F);

    public final static Color BRIGHT_GREEN = new Color(0x00,0xFF,0x00);
    public final static Color BRIGHT_RED = new Color(0xFF,0x00,0x00);

    private static String PADDING = "                                         ";
    private static int MAX_PADDING = PADDING.length();
    
    private static GregorianCalendar GET_DAY_CALENDAR = new GregorianCalendar();

    private static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MMM-yyyy");

    private static SimpleDateFormat MONTH_FORMAT = new SimpleDateFormat("MMM-yyyy");

    private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy");

    public final static int FIRST_MONTH_OF_YEAR = GregorianCalendar.APRIL;

    public static String getDay(Date date) {

        GET_DAY_CALENDAR.setTime(date);
        
        switch (GET_DAY_CALENDAR.get(Calendar.DAY_OF_WEEK)) {
            case Calendar.MONDAY:
                return "Monday";
            case Calendar.TUESDAY:
                return "Tuesday";
            case Calendar.WEDNESDAY:
                return "Wednesday";
            case Calendar.THURSDAY:
                return "Thursday";
            case Calendar.FRIDAY:
                return "Friday";
            case Calendar.SATURDAY:
                return "Saturday";
            case Calendar.SUNDAY:
                return "Sunday";
            default:
                return null;
        }
    }

    public static String getPartOfDay(PartOfDay part) {
        switch (part) {
            case AM:
                return "Morning";
            case PM:
                return "Afternoon";
            case ALL_DAY:
                return "Full Day";
            default:
                return null;
        }
    }

    public static String dateAsString(Date date) {
        return DATE_FORMAT.format(date);
    }

    public static String monthAsString(Date date) {
        return MONTH_FORMAT.format(date);
    }

    public static String yearAsString(Date date) {
        return YEAR_FORMAT.format(date);
    }

    public static GregorianCalendar firstDayOfWeek(Calendar calendar) {

        GregorianCalendar firstDayOfWeek = (calendar == null) ?
                (GregorianCalendar)DaisyChainSystem.getToday().clone() :
                (GregorianCalendar)calendar.clone();

        cleanCalendar(firstDayOfWeek);
        
        while (firstDayOfWeek.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
            firstDayOfWeek.add(Calendar.DATE, -1);
        }
        return firstDayOfWeek;        
    }

    public static Date firstDayOfWeek(Date date) {
        GregorianCalendar cal = new GregorianCalendar();
        cal.setTime(date);
        return firstDayOfWeek(cal).getTime();
    }
    
    public static GregorianCalendar lastDayOfWeek(Calendar calendar) {
        
        GregorianCalendar lastDayOfWeek = (calendar == null) ?
                (GregorianCalendar)DaisyChainSystem.getToday().clone() :
                (GregorianCalendar)calendar.clone();

        cleanCalendar(lastDayOfWeek);

        while (lastDayOfWeek.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) {
            lastDayOfWeek.add(Calendar.DATE, 1);
        }
        return lastDayOfWeek;
    }

    public static GregorianCalendar firstDayOfMonth(Calendar calendar) {

        GregorianCalendar firstDayOfMonth = calendar == null ?
                (GregorianCalendar)DaisyChainSystem.getToday().clone() :
                (GregorianCalendar)calendar.clone();

        cleanCalendar(firstDayOfMonth);

        firstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
        return firstDayOfMonth;
    }

    public static GregorianCalendar lastDayOfMonth(Calendar calendar) {

        GregorianCalendar lastDayOfMonth = firstDayOfMonth(calendar);

        lastDayOfMonth.add(Calendar.MONTH, 1);
        lastDayOfMonth.add(Calendar.DATE, -1);
        return lastDayOfMonth;
    }

    public static GregorianCalendar firstDayOfYear(Calendar calendar) {

        GregorianCalendar firstDayOfYear = calendar == null ?
                (GregorianCalendar)DaisyChainSystem.getToday().clone() :
                (GregorianCalendar)calendar.clone();

        cleanCalendar(firstDayOfYear);

        firstDayOfYear.set(Calendar.MONTH, 0);
        firstDayOfYear.set(Calendar.DAY_OF_MONTH, 1);
        return firstDayOfYear;
    }

    public static GregorianCalendar lastDayOfYear(Calendar calendar) {

        GregorianCalendar lastDayOfYear = calendar == null ?
                (GregorianCalendar)DaisyChainSystem.getToday().clone() :
                (GregorianCalendar)calendar.clone();

        cleanCalendar(lastDayOfYear);

        lastDayOfYear.set(Calendar.MONTH, 11);
        lastDayOfYear.set(Calendar.DAY_OF_MONTH, 31);
        return lastDayOfYear;
    }

    public static GregorianCalendar firstDayOfSystemYear(Calendar calendar) {

        GregorianCalendar firstDayOfYear = calendar == null ?
                (GregorianCalendar)DaisyChainSystem.getToday().clone() :
                (GregorianCalendar)calendar.clone();

        cleanCalendar(firstDayOfYear);

        // If today is before our first month the system year started last year?
        if (firstDayOfYear.get(GregorianCalendar.MONTH) < FIRST_MONTH_OF_YEAR) {
            firstDayOfYear.add(GregorianCalendar.YEAR, -1);
        }
        firstDayOfYear.set(GregorianCalendar.MONTH, FIRST_MONTH_OF_YEAR);
        firstDayOfYear.set(GregorianCalendar.DAY_OF_MONTH, 1);

        return firstDayOfYear;
    }

    public static GregorianCalendar lastDayOfSystemYear(Calendar calendar) {

        GregorianCalendar endOfYear = firstDayOfSystemYear(calendar);
        endOfYear.add(GregorianCalendar.YEAR, 1);
        endOfYear.add(GregorianCalendar.DATE, -1);
        return endOfYear;
    }

    public static GregorianCalendar startOfNextWeek(Calendar calendar) {

        GregorianCalendar startOfNextWeek = lastDayOfWeek(calendar);
        cleanCalendar(startOfNextWeek);

        startOfNextWeek.add(Calendar.DATE, 1);
        return startOfNextWeek;
    }

    public static GregorianCalendar endOfLastWeek(Calendar calendar) {

        GregorianCalendar endOfLastWeek = firstDayOfWeek(calendar);

        endOfLastWeek.add(Calendar.DATE, -1);
        return endOfLastWeek;
    }

    public static ArrayList<DateRange> getFullMonths(DateRange range) {

        ArrayList<DateRange> months = new ArrayList<DateRange>();

        GregorianCalendar startDate = new GregorianCalendar();
        startDate.setTime(range.getFromDate());

        GregorianCalendar endDate = new GregorianCalendar();
        endDate.setTime(range.getToDate());

        while (!startDate.after(endDate)) {

            months.add(new DateRange(
                    firstDayOfMonth(startDate).getTime(),
                    lastDayOfMonth(startDate).getTime()));

            startDate = lastDayOfMonth(startDate);
            startDate.add(Calendar.DATE, 1);
        }

        return months;
    }

    /**
     * Given a date range, this method will return a series of date ranges for
     * full weeks (Monday to Sunday) that "intersect" with the supplied date
     * range.
     *
     * @param range a date range
     * @return an array list of "week" specifications
     */
    public static ArrayList<DateRange> getFullWeeks(DateRange range) {

        ArrayList<DateRange> weeks = new ArrayList<DateRange>();

        GregorianCalendar startDate = new GregorianCalendar();
        startDate.setTime(range.getFromDate());

        GregorianCalendar endDate = new GregorianCalendar();
        endDate.setTime(range.getToDate());

        while (!startDate.after(endDate)) {

            weeks.add(new DateRange(
                    firstDayOfWeek(startDate).getTime(),
                    lastDayOfWeek(startDate).getTime()));

            startDate = startOfNextWeek(startDate);
        }

        return weeks;
    }

    public static ArrayList<DateRange> getClippedWeeks(DateRange range) {

        ArrayList<DateRange> weeks = getFullWeeks(range);

        if (weeks.size() > 0) {
            weeks.get(0).setFromDate(range.getFromDate());
            weeks.get(weeks.size() - 1).setToDate(range.getToDate());
        }

        return weeks;
    }

    public static PartOfDay getDayPart(Date fromDate, Date toDate,
            PartOfDay fromPart, PartOfDay toPart, Date date) {

        // Start of date range is closed and is later than supplied date
        if (fromDate != null && fromDate.after(date)) {
            return null;
        }

        // End of date range is closed and is earlier than supplied date
        if (toDate != null && toDate.before(date)) {
            return null;
        }

        // Is the date at the start of the range?
        if (fromDate != null && fromDate.equals(date)) {
            return fromPart;
        }

        // Is the date at the end of the range?
        if (toDate != null && toDate.equals(date)) {
            return toPart;
        }

        // It's either opene ended or in the middle of the
        // range so we can assume that it's a full day

        return PartOfDay.ALL_DAY;
    }

    public static void cleanCalendar(Calendar cal) {
        cal.set(GregorianCalendar.HOUR_OF_DAY, 0);
        cal.set(GregorianCalendar.MINUTE, 0);
        cal.set(GregorianCalendar.SECOND, 0);
        cal.set(GregorianCalendar.MILLISECOND, 0);        
    }

    public ArrayList<Date> getWeeksCommencingForMonth(Date monthCommencing) {

        GregorianCalendar cal = new GregorianCalendar();
        cal.setTime(monthCommencing);
        int nextMonth = cal.get(GregorianCalendar.MONTH);

        // Go to the start of the month
        cal = firstDayOfMonth(cal);

        // Go to the start of that week
        cal = firstDayOfWeek(cal);

        ArrayList<Date> weeks = new ArrayList<Date>();
        while (cal.get(GregorianCalendar.MONTH) != nextMonth) {
            weeks.add(cal.getTime());
            cal.add(GregorianCalendar.WEEK_OF_YEAR, 1);
        }

        return weeks;
    }

    public static boolean doublesEqual(Double d1, Double d2) {
        if (d1 == d2) {
            return true;
        }
        if ((d1 == null) != (d2 == null)) {
            return false;
        }
        return d1.equals(d2);
    }

    public static boolean nullAwareDoublesEqual(Double d1, Double d2) {
        double sd1 = (d1 != null ? d1 : 0.0);
        double sd2 = (d2 != null ? d2 : 0.0);
        return sd1 == sd2;
    }

    public static Date cleanTime(Date time) {
        if (time == null) {
            return null;
        }
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(time);
        calendar.set(GregorianCalendar.YEAR, 1970);
        calendar.set(GregorianCalendar.DAY_OF_YEAR, 1);
        time.setTime(calendar.getTime().getTime());
        return calendar.getTime();
    }

    public static Date cleanDate(Date date) {
        if (date == null) {
            return null;
        }
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        calendar.set(GregorianCalendar.HOUR_OF_DAY, 0);
        calendar.set(GregorianCalendar.MINUTE, 0);
        calendar.set(GregorianCalendar.SECOND, 0);
        calendar.set(GregorianCalendar.MILLISECOND, 0);
        date.setTime(calendar.getTime().getTime());
        return calendar.getTime();
    }

    public static String padString(String string, int length) {

        int paddingRequired = length - string.length();
        while (paddingRequired > 0) {

            if (paddingRequired > MAX_PADDING) {
                paddingRequired = MAX_PADDING;
            }
            
            string = string.concat(PADDING.substring(0, paddingRequired));            
            paddingRequired = length - string.length();
        }
        
        return string;
    }

    public static DateRange getLastYear() {
        GregorianCalendar cal = new GregorianCalendar();
        cal.add(GregorianCalendar.YEAR, -1);
        return new DateRange(
                firstDayOfYear(cal).getTime(),
                lastDayOfYear(cal).getTime());
    }

    public static DateRange getThisYear() {
        GregorianCalendar cal = new GregorianCalendar();
        return new DateRange(
                firstDayOfYear(cal).getTime(),
                lastDayOfYear(cal).getTime());
    }

    public static DateRange getNextYear() {
        GregorianCalendar cal = new GregorianCalendar();
        cal.add(GregorianCalendar.YEAR, 1);
        return new DateRange(
                firstDayOfYear(cal).getTime(),
                lastDayOfYear(cal).getTime());
    }

    public static DateRange getLastSystemYear() {
        GregorianCalendar cal = new GregorianCalendar();
        cal.add(GregorianCalendar.YEAR, -1);
        return new DateRange(
                firstDayOfSystemYear(cal).getTime(),
                lastDayOfSystemYear(cal).getTime());
    }

    public static DateRange getThisSystemYear() {
        GregorianCalendar cal = new GregorianCalendar();
        return new DateRange(
                firstDayOfSystemYear(cal).getTime(),
                lastDayOfSystemYear(cal).getTime());
    }

    public static DateRange getNextSystemYear() {
        GregorianCalendar cal = new GregorianCalendar();
        cal.add(GregorianCalendar.YEAR, 1);
        return new DateRange(
                firstDayOfSystemYear(cal).getTime(),
                lastDayOfSystemYear(cal).getTime());
    }

    public static double getTimeOnlyAsDouble(Date date) {
        
        if (date == null) {
            return 0.0;
        } else {
            GregorianCalendar cal = new GregorianCalendar();
            cal.setTime(date);
            double hours = cal.get(GregorianCalendar.HOUR_OF_DAY);
            double seconds = cal.get(GregorianCalendar.MINUTE);
            return hours + seconds / 60;
        }
    }

    public static Date glueDateAndTime(Date date, double time) {

        GregorianCalendar cal = new GregorianCalendar();
        cal.setTime(date);
        cal.add(GregorianCalendar.MINUTE, (int)Math.round(time * 60.0));
        return cal.getTime();
    }
}